package com.rpay.admin.config.security;

import com.rpay.dao.mapper.RpayAdminModuleMapper;
import com.rpay.dao.mapper.RpayAdminRoleMapper;
import com.rpay.model.admin.RpayAdminModule;
import com.rpay.model.admin.RpayAdminRole;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.util.*;


/**
 * 最核心的地方，就是提供某个资源对应的权限定义，即getAttributes方法返回的结果。
 * 有两种方式
 * 1）只取到访问的资源是否需要权限以及需要的什么权限
 * 2）所有资源的权限都取到
 * <p/>
 * 在本方法中是采用第二种，此类在初始化时，取到所有资源及其对应角色的定义。
 * <p>
 * 此方法有个问题是应用在启动时默认把所有的资源都放入了内存中，那么后续如何涉及到资源变更时也需要考虑到内存中相应的变更，否则会出问题
 */
@Service
public class CustomInvocationSecurityMetadataSourceService implements
        FilterInvocationSecurityMetadataSource {

    private static Map<String, Collection<ConfigAttribute>> resourceMap = null;
    //TODO 也可以考虑模仿JdbcTokenRepositoryImpl写sql在java类中执行 缺点是后期维护时需要关注这块，
    // 优点是下一个项目复制时 不会发生xml文件误删
    @Autowired
    private RpayAdminModuleMapper secPermissionMapper;
    @Autowired
    private RpayAdminRoleMapper secRoleMapper;
  
    /*public CustomInvocationSecurityMetadataSourceService(SResourceService sres,SRoleService sR) { 
        this.sResourceService = sres; 
        this.sRoleService = sR; 
        loadResourceDefine(); 
    }*/

    @PostConstruct
    private void loadResourceDefine() {
        // 在Web服务器启动时，提取系统中的所有权限。</span>
        List<RpayAdminRole> secRoleList = secRoleMapper.selectAll();
        resourceMap = new HashMap<String, Collection<ConfigAttribute>>();

        for (RpayAdminRole auth : secRoleList) {
            ConfigAttribute ca = new SecurityConfig(auth.getRoleId().toString());
            List<RpayAdminModule> secPerMissionList = secPermissionMapper.findTModuleList(auth);
            for (RpayAdminModule res : secPerMissionList) {
                String url = res.getUrl();
                if (resourceMap.containsKey(url)) {
                    Collection<ConfigAttribute> value = resourceMap.get(url);
                    if (!value.contains(ca)) {
                        value.add(ca);
                        resourceMap.put(url, value);
                    }
                } else {
                    Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>();
                    if (!atts.contains(ca)) {
                        atts.add(ca);
                        resourceMap.put(url, atts);
                    }
                }
            }
        }
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return new ArrayList<ConfigAttribute>();
    }

    // 根据URL，找到相关的权限配置。</span>
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object)
            throws IllegalArgumentException {
        FilterInvocation filterInvocation = (FilterInvocation) object;
        if (resourceMap == null) {
            loadResourceDefine();
        }
        Iterator<String> ite = resourceMap.keySet().iterator();
        while (ite.hasNext()) {
            String resURL = ite.next();
            RequestMatcher requestMatcher = new AntPathRequestMatcher(resURL);
            if (requestMatcher.matches(filterInvocation.getHttpRequest())) {
                return resourceMap.get(resURL);
            }
        }
        return null;
    }

    @Override
    public boolean supports(Class<?> arg0) {

        return true;
    }

    /**
     * 当角色变化时，重新加载一下资源
     * <p>
     * 暂时先全部加载，等后续角色数量上来了 再考虑优化
     * 此方法存在多人同时操作时 会有bug出现 例如resourceMap无任何值
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void changeResouceMap() {
        this.loadResourceDefine();
    }

}  